package com.onpositive.data.serializer.writers;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import javaewah.EWAHCompressedBitmap;

import com.onpositive.data.serializer.readers.DefaultReaderFactory;

public class NumericValueCollectionWriter extends AbstractWriter<Number> {
	
	private static final double acceptableRatio = 0.001 ;
	public final static byte ONE_BYTE_PER_RECORD_STORE = 1;
	public final static byte TWO_BYTE_PER_RECORD_STORE = 2;
	public final static byte INTEGER_STORAGE = 4;
	public static final int DOUBLE_STORE = 16;

	public NumericValueCollectionWriter() {
		super( Number.class );
	}

	ArrayList<Number> storage = new ArrayList<Number>() ;
	
	@Override
	public void append(Number value) {
		storage.add( value ) ;		
	}
	
	@Override
	protected int getID() {
		return DefaultReaderFactory.RW_ID_NUMBER_QUANIFIER ;
	}

	@Override
	protected void doSerialize() {
		
		int size = storage.size();
		int nonZeroRecordsCount = 0 ;
		EWAHCompressedBitmap emptyCells = new EWAHCompressedBitmap();
		
		double minValue = Double.MAX_VALUE ;
		double firstValue = Double.MAX_VALUE ;
		double maxValue = Double.MIN_VALUE ;
		
		double[] dValues = new double[ size ] ;
		boolean[] flags = new boolean[ size] ;
		
		for( int i = 0 ; i < size ; i++ )
		{
			Number num = storage.get(i) ;
			if( num != null )
			{
				flags[i] = true ;
				double dVal = num.doubleValue() ;
				dValues[i] = dVal ;
				if( dVal < minValue )
				{
					firstValue = minValue ;
					minValue = dVal ;
				}
				else if( dVal != minValue )
					firstValue = dVal < firstValue ? dVal : firstValue ;
				
				//maxValue = maxValue > dVal ? maxValue : dVal ;
				if( maxValue < dVal )
					maxValue = dVal ;
				
				nonZeroRecordsCount++ ;
			}
			else{
				emptyCells.set(i) ;
				flags[i] = false ;
			}
		}
		
		double shift = ( maxValue - minValue )/( ( 1<<( 8 ) )-1 ) ;
		
		boolean useOneByte = shift < acceptableRatio * ( minValue >= 0 ? minValue : (-1)*firstValue ) ;
		if( !useOneByte )
			shift = ( maxValue - minValue )/( ( 1<<( 8*2 ) )-1 ) ;
		
		byte[] packedValues1 = null ;
		char[] packedValues2 = null ;
		double[] pv3;
		int recInd = 0 ;
		
		
		boolean isInteger = true ;
//		if( useOneByte ){
//			packedValues1 = new byte[ nonZeroRecordsCount ] ;
//		
//			for( int i = 0 ; i < size ; i++ )
//			{
//				if( flags[i] )
//				{
//					isInteger = isInteger && isIntegr( dValues[i] ) ;
//					int iVal = (int)(( dValues[i] - minValue )/ shift + 0.5 ) ;
//					packedValues1[ recInd   ] = 0 ;
//					packedValues1[ recInd++ ] = (byte)( iVal + Byte.MIN_VALUE ) ;				
//				}
//			}
//		}
//		else{			
//			packedValues2 = new char[ nonZeroRecordsCount ] ;
//			for( int i = 0 ; i < size ; i++ )
//			{
//				if( flags[i] )
//				{
//					double dVal = dValues[i] ;
//					isInteger = isInteger && isIntegr( dVal ) ;
//					int iVal = (int)(( dValues[i] - minValue )/ shift + 0.5 ) ;
//					packedValues2[ recInd   ] = 0 ;
//					packedValues2[ recInd++ ] = (char)( iVal + Character.MIN_VALUE ) ;
//					//test = packedValues2[ recInd-1 ] ; 
//				}
//			}
////			System.out.print( test ) ;
//		}
		pv3=new double[nonZeroRecordsCount];
		recInd=0;
		for( int i = 0 ; i < size ; i++ )
		{
			if( flags[i] )
			{
				double dVal = dValues[i] ;
				isInteger = isInteger && isIntegr( dVal ) ;
				
				pv3[ recInd++ ] = dVal;
				//test = packedValues2[ recInd-1 ] ; 
			}
		}
		int storageType = useOneByte ? ONE_BYTE_PER_RECORD_STORE : TWO_BYTE_PER_RECORD_STORE;
		storageType |= (isInteger ? INTEGER_STORAGE : 0  ) ; 
		if (true){
			storageType=DOUBLE_STORE;
		}
		int precision = computePrecision( shift ) ;
		if( isInteger && precision < 0 )
			precision = 0 ;
		
		super.writeIntToStream( size ) ;		
		super.writeIntToStream( storageType ) ;
		super.writeIntToStream( precision ) ;
		super.writeDoubleToStream( minValue ) ;
		super.writeDoubleToStream( shift ) ;
		
		writeEmptyCells( emptyCells ) ;
		if (storageType==DOUBLE_STORE){
			for (double m:pv3){
				super.writeDoubleToStream(m);
			}
			return;
		}
//		if( useOneByte ){			
//			for( byte bt : packedValues1 )
//				super.writeByteToStream( bt ) ;
//		}
//		else
//			for( char ch : packedValues2 )
//				super.writeCharToStream( ch ) ;		
	}
	
	private void writeEmptyCells( EWAHCompressedBitmap emptyCells )
	{
		ByteArrayOutputStream byteArrOS = new ByteArrayOutputStream() ;
		DataOutputStream OS = new DataOutputStream( byteArrOS ) ;
		try {
			emptyCells.serialize( OS ) ;
		} catch (IOException e) {
			e.printStackTrace();
		}
		byte[] bArr = byteArrOS.toByteArray() ;
		
		super.writeIntToStream( bArr.length );
		super.writeByteArrayToStream( bArr ) ;
	}
	

	private static final double epsilon = 0.0001 ; 
	private boolean isIntegr(double d)
	{
		int round =  (int)( d + 0.5 ) ;
		return Math.abs( d - round ) < epsilon ;
	}
	
	private int computePrecision(double shift) {
		
		if( shift < epsilon * epsilon )
			return 0 ;
		
		double log = Math.log10(shift) ;
		if( (Double)log == Double.NaN || log == Double.NEGATIVE_INFINITY || log == Double.POSITIVE_INFINITY )
			return 0 ;
		
		int iLog = (int)( log + epsilon ) ;
		if( iLog < log )
			iLog ++ ;
		
		if( iLog > -1 )
			iLog-- ;
		
		return iLog ;
	}

	
}
